package com.shareware.common.secure.keystore

import android.annotation.TargetApi
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import com.shareware.common.utils.hexToByteArray
import com.shareware.common.utils.log
import com.shareware.common.utils.toHexString
import java.security.KeyStore
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec

/**
 * desc: 功能描述
 * email: mobiledeveloper@qq.com
 */
class AesCbcKS private constructor() {

    companion object {
        private const val ALGORITHM_NAME = "AndroidKeyStore"
        private const val TRANSFORMATION_CBC = "AES/CBC/PKCS7Padding"
        private const val TAG = "AesCbcKS"

        //AES密钥长度为128bit、192bit、256bit，默认为128bit，一个字节8位
        private const val KEYSTORE_SIZE = 256

        private const val IV_CBC_DEFAULT_SIZE = 16

        @TargetApi(Build.VERSION_CODES.M)
        fun encrypt(content: String, key: String): String {
            return if (content.isNotEmpty() && key.isNotEmpty()) {
                encrypt(content.toByteArray(), key)
            } else {
                log(TAG, "cbc encrypt param is not right")
                ""
            }
        }

        @TargetApi(Build.VERSION_CODES.M)
        fun encrypt(contentBytes: ByteArray, key: String): String {
            return if (contentBytes.isNotEmpty() && key.isNotEmpty()) {
                try {
                    val cipher = Cipher.getInstance(TRANSFORMATION_CBC)
                    val secretKey = generateSecretKey(key)
                    if (secretKey == null) {
                        log(TAG, "generateSecretKey is null")
                        return ""
                    }
                    cipher.init(Cipher.ENCRYPT_MODE, secretKey)
                    val encryptBytes = cipher.doFinal(contentBytes)
                    val ivBytes = cipher.iv
                    if (ivBytes == null || ivBytes.size != IV_CBC_DEFAULT_SIZE) {
                        log(TAG, "iv is invalid")
                        return ""
                    }
                    val result = Arrays.copyOf(ivBytes, ivBytes.size + encryptBytes.size)
                    System.arraycopy(encryptBytes, 0, result, ivBytes.size, encryptBytes.size)
                    result.toHexString()
                } catch (ex: Exception) {
                    ""
                }
            } else {
                log(TAG, "cbc encrypt param is not right")
                ""
            }
        }

        @Synchronized
        @TargetApi(Build.VERSION_CODES.M)
        private fun generateSecretKey(keyString: String): SecretKey? {
            return try {
                val keyStore = KeyStore.getInstance(ALGORITHM_NAME)
                keyStore.load(null)
                val key = keyStore.getKey(keyString, null)
                if (key != null && key is SecretKey) {
                    key
                } else {
                    val keyGenerator = KeyGenerator.getInstance("AES", ALGORITHM_NAME)
                    val params = KeyGenParameterSpec.Builder(keyString, (KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT))
                            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                            .setKeySize(KEYSTORE_SIZE)
                            .build()
                    keyGenerator.init(params)
                    keyGenerator.generateKey()
                }
            } catch (ex: Exception) {
                null
            }
        }

        @TargetApi(Build.VERSION_CODES.M)
        fun decrypt(cipherText: String, key: String): String {
            return if (cipherText.isNotEmpty() && key.isNotEmpty()) {
                val contentBytes = cipherText.hexToByteArray()
                decrypt(contentBytes, key)
            } else {
                log(TAG, "params is invalid")
                ""
            }
        }

        @TargetApi(Build.VERSION_CODES.M)
        fun decrypt(contentBytes: ByteArray, key: String): String {
            return if (key.isNotEmpty() && contentBytes.size > IV_CBC_DEFAULT_SIZE) {
                val secretKey = generateSecretKey(key)
                if (secretKey == null) {
                    log(TAG, "decrypt secret key is null")
                    return ""
                }
                val ivBytes = contentBytes.copyOf(IV_CBC_DEFAULT_SIZE)
                try {
                    val cipher = Cipher.getInstance(TRANSFORMATION_CBC)
                    cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(ivBytes))
                    cipher.doFinal(contentBytes, IV_CBC_DEFAULT_SIZE, contentBytes.size - IV_CBC_DEFAULT_SIZE).toString(Charsets.UTF_8)
                } catch (ex: Exception) {
                    log(TAG, "${ex.message}")
                    ""
                }
            } else {
                log(TAG, "decrypt source or key is invalid")
                ""
            }
        }
    }
}